import CryptoJS from 'crypto-js'

/**
 * Decrypts a string using AES-CBC
 * @param word The encrypted string to decrypt
 * @param aesKey The encryption key (UTF-8)
 * @param ivKey The initialization vector (UTF-8)
 * @returns The decrypted string
 */
export function Decrypt(word: string, aesKey: string, ivKey: string): string {
  const encryptedHexStr = CryptoJS.enc.Hex.parse(word)
  const srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr)
  const key = CryptoJS.enc.Utf8.parse(aesKey)
  const iv = CryptoJS.enc.Utf8.parse(ivKey)

  const decrypt = CryptoJS.AES.decrypt(srcs, key, {
    iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
  })

  return decrypt.toString(CryptoJS.enc.Utf8)
}

/**
 * Encrypts a string using AES-CBC
 * @param word The string to encrypt
 * @param aesKey The encryption key (UTF-8)
 * @param ivKey The initialization vector (UTF-8)
 * @returns The encrypted string in hex format
 */
export function Encrypt(word: string, aesKey: string, ivKey: string): string {
  const srcs = CryptoJS.enc.Utf8.parse(word)
  const key = CryptoJS.enc.Utf8.parse(aesKey)
  const iv = CryptoJS.enc.Utf8.parse(ivKey)

  const encrypted = CryptoJS.AES.encrypt(srcs, key, {
    iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
  })

  return encrypted.ciphertext.toString()
}

/**
 * Creates a signature for parameters
 * @param params Array of [key, value] tuples
 * @param key The secret key for signing
 * @returns MD5 hash of the signed parameters in uppercase
 */
export function createSign(params: Array<[string, unknown]>, key: string): string {
  const sortedParams = [...params].sort()
  let paramVals = ''

  for (const [paramKey, paramValue] of sortedParams) {
    if (
      paramValue !== null &&
      paramValue !== undefined &&
      paramValue !== '' &&
      typeof paramValue !== 'object'
    ) {
      paramVals += `${paramKey}=${paramValue}&`
    }
  }

  paramVals += `key=${key}`
  const hash = CryptoJS.MD5(paramVals)
  return hash.toString().toUpperCase()
}

/**
 * Signs parameters by adding a signature
 * @param params The parameters object to sign
 * @param appKey The application key for signing
 * @returns The parameters object with added signature
 */
export function signParams<T extends Record<string, unknown>>(
  params: T & { sign?: string },
  appKey: string,
): T & { sign: string } {
  const entries = Object.entries(params)
    .filter(([key]) => key !== 'sign') // Exclude existing sign if present
    .map(([key, value]) => [key, value] as [string, unknown])

  const sign = createSign(entries, appKey)
  return { ...params, sign }
}
